package vista.objetos;

import java.awt.event.MouseAdapter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import javax.media.opengl.GLAutoDrawable;
import javax.swing.JFrame;
import javax.swing.tree.DefaultMutableTreeNode;

import controlador.ContenedorDeErrores;
import controlador.NoExisteEntidadGraficable;

import vista.paneles.hojaDibujo.OrientacionHoja;
import vista.treeDiagrama.NodoDiagrama;
import vista.treeDiagrama.NodoTreeDiagrama;

import logica.Diagrama;
import logica.Entidad;
import logica.EntidadRepetidaException;
import logica.EntidadRol;
import logica.IdExternoInexistenteException;
import logica.ObjetoDiagrama;
import logica.Relacion;
import logica.persistencia.DiagramaGraficablePersistente;
import memento.EstadoGuardable;
import memento.IMemento;
import memento.MementoDiagramaGraficable;

public class DiagramaGraficable implements Graficable, Comparator<GraficableConEtiqueta>, IContenedorDeGraficable, EstadoGuardable{
	
	private Diagrama diagrama;
	private List<DiagramaGraficable> diagHijos = new LinkedList<DiagramaGraficable>();
	private List<GraficableConEtiqueta> objetos = new LinkedList<GraficableConEtiqueta>();
	private DefaultMutableTreeNode nodo = null;
	private OrientacionHoja orientacion;

	private boolean soyElDiagramaActual;
	
	public DiagramaGraficable(Diagrama diagrama) 
	{
		this.diagrama = diagrama;
		this.setOrientacion(OrientacionHoja.SENTIDO_VERTICAL);
		this.setSoyElDiagramaActual(false);
	}

	/*para crear un diagrama graficable a partir de un diagrama ya existente con objetos graficables*/
	public DiagramaGraficable(Diagrama diagrama, List<GraficableConEtiqueta> objetosACopiar) throws EntidadRepetidaException, IdExternoInexistenteException
	{
		this(diagrama);
		copiar(objetosACopiar);
	}
	
	private void copiar(List<GraficableConEtiqueta> objetosACopiar) throws EntidadRepetidaException, IdExternoInexistenteException 
	{
		Iterator<GraficableConEtiqueta> itGraf = objetosACopiar.iterator();
		
		while(itGraf.hasNext())
		{
			GraficableConEtiqueta graf = itGraf.next();
			graf.copiarse(this);
//			this.getDiagrama().agregarObjetoDiagrama(graf.getObjetoDiagramaDecorado());
		}
	}

	@Override
	public List<GraficableConEtiqueta> getGraficables(){
		return objetos;
	}
	
	public void verificarSiGraficableRepetido(GraficableConEtiqueta graficable) throws ObjetoGraficableRepetidoException, NoEsObjetoDiagramableException
	{
		Iterator<GraficableConEtiqueta> itGraf = this.getGraficables().iterator();
		
		while(itGraf.hasNext())
		{
			GraficableConEtiqueta grafDeDiag = itGraf.next();
			
			if (graficable.esElGraficableDe(grafDeDiag.getObjetoDiagramaDecorado()))
				throw new ObjetoGraficableRepetidoException("El objeto que se intenta agregrar ya existe en " +
						"el diagrama. Ver: " + grafDeDiag.getObjetoDiagramaDecorado().getNombre());
		}
	}
	
	public void borrarseDeLaHoja()
	{
		Iterator<GraficableConEtiqueta> itGraf = this.getGraficables().iterator();
		
		while(itGraf.hasNext())
			itGraf.next().borrarseDeLaHoja();
	}
	
	@Override
	public void agregarGraficable(GraficableConEtiqueta graficable)
	{
		graficable.agregarseAlDiagrama(this);
		//TODO agregar al diag Lógico	--> no debería, 
		
		Collections.sort(this.getGraficables(), this);
	}
	
	/* se borran todos los graficable del diagrama graficable. Se borran 
	 * el objeto lógico también del diagrama lógico
	 */
	public void borrarGraficable(GraficableConEtiqueta graficable) throws NoEsObjetoDiagramableException
	{
		ObjetoDiagrama objeto = graficable.getObjetoDiagramaDecorado();
		Iterator<GraficableConEtiqueta> itGraf = objeto.getGraficables().iterator();
		GraficableConEtiqueta graficableBorrar;
		
		while(itGraf.hasNext())
		{
			graficableBorrar = itGraf.next();
			
			graficableBorrar.borrarseDeLaHoja();									// lo borro de la vista si está
			graficableBorrar.getDiagrama().getGraficables().remove(graficableBorrar);	// borro el graficable del diagrama gráfico
			graficableBorrar.eliminarNodo();										// borro el nodo que lo representa en el Jtree
			itGraf.remove();														// borro la vista en el objeto lógico
		}
		
		
		if(objeto.getGraficables().size() == 0) // o sea, si ya no tiene vistas
			objeto.eliminaseDelDiagrama();
	}
	
	public Diagrama getDiagrama() {
		return this.diagrama;
	}

	public void setDiagrama(Diagrama diag) {
		this.diagrama = diag;
	}
	
	public void agregarDiagramaHijo(DiagramaGraficable diag){
		this.diagHijos.add(diag);
	}
	
	public List<DiagramaGraficable> getDiagramasHijos(){
		return this.diagHijos;
	}
	
	public NodoTreeDiagrama getNodoTreeDiagrama() {
		return new NodoDiagrama(this);
	}

	public DefaultMutableTreeNode getNodo() {
		return nodo;
	}

	public void setNodo(DefaultMutableTreeNode nodo) {
		this.nodo = nodo;
	}

	@Override
	public ObjetoDiagrama getObjetoDiagramaDecorado() throws NoEsObjetoDiagramableException {
		throw new NoEsObjetoDiagramableException("Un diagrama no decora un ObjetoDiagrama.");
	}

	@Override
	public MouseAdapter getEventoEditarGraficable(JFrame ventanaPadre) {
		// TODO Editar diag graf?
		return null;
	}

	@Override
	public void dibujarse(Object areaDeDibujo) 
	{
		GLAutoDrawable gLDrawable = (GLAutoDrawable) areaDeDibujo;
		
		for(Iterator<GraficableConEtiqueta> it = this.getGraficables().iterator(); it.hasNext() == true;)
			it.next().dibujarse(gLDrawable);    
	}

	@Override
	public boolean esElGraficableDe(ObjetoDiagrama objeto) {
		return false;
	}

	public  DiagramaGraficablePersistente getDiagramaPersistente() {
		return new DiagramaGraficablePersistente(this);
	}

	@Override
	public void eliminarNodo() {
		// TODO eliminar diagrama?
		
	}

	/*
	 * Este método hace que la lista de objetos esté ordenada de la siguiente manera:
	 * 		- relación
	 * 		- jerarquía
	 * 		- entidad
	 * 
	 * de esta manera, primero se dibujan las relaciones, luego la jerarquia y último las
	 * entidades, es para evitar que las líneas de las relaciones y las de la jerarquia
	 * se superpongan al dibujo de la entidad y tenga que volver a dibujar la entidad.
	 */
    public int compare(GraficableConEtiqueta graf1, GraficableConEtiqueta graf2) {
    	return -graf1.getClass().toString().compareToIgnoreCase(graf2.getClass().toString());
	}

    public EntidadRolGraficable getEntidadRolFuerteDebil(Entidad entidadFuerte, Entidad entidadDebil) throws IdExternoInexistenteException
    {
		try
		{
	    	for (GraficableConEtiqueta objeto: this.getGraficables())
	    	{
	    		if (objeto instanceof RelacionGraficable )
	    		{
	    			RelacionGraficable relacionGraf = (RelacionGraficable)objeto;
	    			Relacion relacion = (Relacion)objeto.getObjetoDiagramaDecorado();
					
	   				if (relacion.tieneEstaEntidad(entidadFuerte) && relacion.tieneEstaEntidad(entidadDebil))
	   					return buscarEntidadRol(entidadFuerte, relacionGraf);
	    		}
	    	}
    	}
    	catch (NoEsObjetoDiagramableException e) 
    	{	//no podría pasar
			e.printStackTrace();
		}
    	
    	throw new IdExternoInexistenteException("No existe una relación entre: <" + entidadDebil + "> y <"+ entidadFuerte + ">."); 
    }

    public RelacionGraficable getRelacionFuerteDebil(Entidad entidadFuerte, Entidad entidadDebil)
    {
		try
		{
	    	for (GraficableConEtiqueta objeto: this.getGraficables())
	    	{
	    		if (objeto instanceof RelacionGraficable )
	    		{
	    			RelacionGraficable relacionGraf = (RelacionGraficable)objeto;
	    			Relacion relacion = (Relacion)objeto.getObjetoDiagramaDecorado();
					
	   				if (relacion.tieneEstaEntidad(entidadFuerte) && relacion.tieneEstaEntidad(entidadDebil))
	   					return relacionGraf;
	    		}
	    	}
    	}
    	catch (NoEsObjetoDiagramableException e) 
    	{	//no podría pasar
			e.printStackTrace();
		}
    	return null;
    }
       
	private EntidadRolGraficable buscarEntidadRol(Entidad entidadDebil, RelacionGraficable relacionGraf) throws IdExternoInexistenteException 
	{
		for (EntidadRolGraficable entidadRol: relacionGraf.getReferenciasExternas())
		{
			if (entidadRol.getEntidad().esElGraficableDe(entidadDebil))
				return entidadRol;
		}
		
		throw new IdExternoInexistenteException("No existe entidad rol correspondiente a la entidad: " + entidadDebil);
	}	

	/*
	 * true si el diagrama tiene ese graficable. => misma instancia
	 */
	public boolean tieneElGraficable (GraficableConEtiqueta graficable)
	{
		for (GraficableConEtiqueta grafDeDiag: this.getGraficables())
			if (graficable == grafDeDiag)
				return true;
		
		return false;
	}
	
	/*
	 * true si el diagrama tiene un graficable que represente esa entidad
	 */
	public boolean tieneUnGraficableDe (Entidad entidad)
	{
		for (GraficableConEtiqueta grafDeDiag: this.getGraficables())
			if (grafDeDiag.esElGraficableDe(entidad))
				return true;
		
		return false;
	}
	
	/*devuelve la entidad graficable correspondiente a la entidad lógica pasada por parámetro
	 * 
	 * 	Entidad (existente en el modelo) --> EntidadGraficable
	 */
	public EntidadGraficable getEntidadGraficableExistente(Entidad entidad) throws NoExisteEntidadGraficable
	{
		Iterator<GraficableConEtiqueta> itGraficable = this.getGraficables().iterator();
		
		while (itGraficable.hasNext())
		{
			Graficable graficable = itGraficable.next();
			
			if (graficable.esElGraficableDe(entidad) == true)
				return (EntidadGraficable)graficable;			
		}

		throw new NoExisteEntidadGraficable("No existe en el diagrama "+this.diagrama.getNombre() + " una EntidadGraficable que represente a la Entidad: " + entidad.getNombre());
	}
	
	/*
	 * Lista de EntidadRol --> Lista de EntidadRolGraficable
	 */
	public List<EntidadRolGraficable> getEntidadRolGraficables(List<EntidadRol> entidadRoles) throws NoExisteEntidadGraficable 
	{
		List<EntidadRolGraficable> entidadesRolesGraficables = new LinkedList<EntidadRolGraficable>();
		Iterator<EntidadRol> itEntidadRol = entidadRoles.iterator();
		
		while (itEntidadRol.hasNext())
		{
			EntidadRol entidadRol = (EntidadRol) itEntidadRol.next();
			EntidadGraficable entidadGraf = this.getEntidadGraficableExistente(entidadRol.getEntidad());
			EntidadRolGraficable entidadRolGraficable = new EntidadRolGraficable(entidadRol, entidadGraf);
			
			entidadesRolesGraficables.add(entidadRolGraficable);
		}
		
		return entidadesRolesGraficables;
	}

	public void validarSoloDiagrama(ContenedorDeErrores errores)
	{
		if (this.getDiagrama().getDiagramasHijo().size() != this.getDiagramasHijos().size())
			errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getNombre() + ">> - No coincide la cantidad de diagramas hijos del proyecto graficable y el proyecto lógico");
		
		for (Diagrama diagrama: this.getDiagrama().getDiagramasHijo())
			this.verificarSiEstaElHijo(diagrama, errores);
		
		for (ObjetoDiagrama objeto: this.getDiagrama().getObjetosDiagramas())
			this.verificarObjeto(objeto, errores);

		for (GraficableConEtiqueta graficable: this.getGraficables())
			graficable.validar(errores);	
		
		this.verificarSiLasEntidadesEstanEnUnaRelacion(errores);
		
		this.verificarSiHayJerarquiaCiclica(errores);
	}
	
	@Override
	public void validar(ContenedorDeErrores errores)
	{
		this.validarSoloDiagrama(errores);
		
		for (DiagramaGraficable diagramaGraficable: this.getDiagramasHijos())
			diagramaGraficable.validar(errores);
	}

	private void verificarObjeto(ObjetoDiagrama objeto, ContenedorDeErrores errores)
	{
		try 
		{
			for (GraficableConEtiqueta graficable: this.getGraficables())
			{
				if (graficable.getObjetoDiagramaDecorado() == objeto)
					return;
			}
		} catch (NoEsObjetoDiagramableException e) {
			e.printStackTrace();
		}
		
		errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getNombre() + ">> - No existe en este diagrama graficable un graficable que represente al objeto lógico: " + objeto.getClass() + " <<" + objeto.getNombre() + ">>");

	}
	
	private void verificarSiEstaElHijo(Diagrama diagrama, ContenedorDeErrores errores)
	{
		for (DiagramaGraficable diagramaGraficable: this.getDiagramasHijos())
		{
			if (diagrama == diagramaGraficable.getDiagrama())
				return;
		}
		
		errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getNombre() + ">> - No existe un diagrama graficable hijo de este diagrama graficable que represente al diagrama lógico: <<" + diagrama.getNombre() + ">>");
	}

	public boolean soyElDiagramaActual() {
		return soyElDiagramaActual;
	}

	public void setSoyElDiagramaActual(boolean soyElDiagramaActual) {
		this.soyElDiagramaActual = soyElDiagramaActual;
	}

	public OrientacionHoja getOrientacion() {
		return orientacion;
	}

	public void setOrientacion(OrientacionHoja orientacion) {
		this.orientacion = orientacion;
	}
	
	@Override
	public boolean estaElGraficable(GraficableConEtiqueta graficable)
	{
		try 
		{
			for (GraficableConEtiqueta graficableEnContenedor: this.getGraficables())
					if (graficableEnContenedor.getObjetoDiagramaDecorado() == graficable.getObjetoDiagramaDecorado())
						return true;
		} catch (NoEsObjetoDiagramableException e) {
				e.printStackTrace();
		}
		
		return false;
	}

	@Override
	public GraficableConEtiqueta getGraficableCopiaDe(GraficableConEtiqueta graficable) 
	{
		try 
		{
			for (GraficableConEtiqueta graficableEnContenedor: this.getGraficables())
					if (graficableEnContenedor.getObjetoDiagramaDecorado() == graficable.getObjetoDiagramaDecorado())
						return graficableEnContenedor;
		} catch (NoEsObjetoDiagramableException e) {
				e.printStackTrace();
		}
		
		return null;
	}

	@Override
	public void agregarObjeto(ObjetoDiagrama objeto)
	{
		try {
			this.getDiagrama().agregarObjetoDiagrama(objeto);
		} catch (EntidadRepetidaException | IdExternoInexistenteException e) { //si pasa está mal programado
			e.printStackTrace();
		}
	}

	private void verificarSiLasEntidadesEstanEnUnaRelacion(ContenedorDeErrores errores) 
	{
		for (EntidadGraficable entidad: this.getEntidades())
			if ( !algunaRelacionTieneLaEntidad(entidad) )
				errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getNombre() + ">> - La entidad <<" + entidad.getObjetoDiagramaDecorado().getNombre() + ">> no pertenece a ninguna relación.");
	}
	
	private boolean algunaRelacionTieneLaEntidad(EntidadGraficable entidad)
	{
		for (RelacionGraficable relacion: this.getRelaciones())
			if ( relacion.tieneLaEntidad(entidad) )
				return true;
		
		return false;
	}
	
	private List<EntidadGraficable> getEntidades()
	{
		List<EntidadGraficable> entidades = new LinkedList<>();
		
		for (GraficableConEtiqueta graficable: this.getGraficables())
			if ( graficable instanceof EntidadGraficable )
				entidades.add( (EntidadGraficable) graficable );

		return entidades;
	}
	
	private List<RelacionGraficable> getRelaciones()
	{
		List<RelacionGraficable> relacion = new LinkedList<>();
		
		for (GraficableConEtiqueta graficable: this.getGraficables())
			if ( graficable instanceof RelacionGraficable )
				relacion.add( (RelacionGraficable) graficable );

		return relacion;
	}
	
	private List<JerarquiaGraficable> getJerarquiaGraficables()
	{
		List<JerarquiaGraficable> jerarquia = new LinkedList<>();
		
		for (GraficableConEtiqueta graficable: this.getGraficables())
			if ( graficable instanceof JerarquiaGraficable )
				jerarquia.add( (JerarquiaGraficable) graficable );

		return jerarquia;
	}
	
	private void verificarSiHayJerarquiaCiclica(ContenedorDeErrores errores)
	{
		List<JerarquiaGraficable> jerarquias1 = this.getJerarquiaGraficables();
		
		if (jerarquias1.size() == 0 || jerarquias1.size() == 1)
			return;
		
		buscarCiclo( errores, jerarquias1.get(0), new Stack<JerarquiaGraficable>() );
	}
	
	/*
	 * solo busca un ciclo.
	 */
	private void buscarCiclo ( ContenedorDeErrores errores, JerarquiaGraficable jerarquiaAnterior, Stack<JerarquiaGraficable> antecesores )
	{
		for ( JerarquiaGraficable jerarquia: this.getJerarquiaGraficables() )
		{
			if ( jerarquiaAnterior.tieneLaEntidad(jerarquia.getEntidadPadre()) && 
					jerarquiaAnterior.getEntidadPadre() != jerarquia.getEntidadPadre() )
			{
				if ( estaEnElStack( antecesores, jerarquia ) )
				{
					errores.agregarError(generarMensajeDeErrorJerarquiaCiclica(antecesores));
					return;
				}
				else
				{
					antecesores.push(jerarquia);
					this.buscarCiclo(errores, jerarquia, antecesores);
				}
			}
		}
		
		antecesores.pop();
	}

	private String generarMensajeDeErrorJerarquiaCiclica(Stack<JerarquiaGraficable> antecesores) 
	{
		String error = "Diagrama graficable: <<" + this.getDiagrama().getNombre() + ">> - Tiene una jerarquía cíclica. Ver las siguientes jerarquías:";
		
		for ( JerarquiaGraficable jerarquia: antecesores )
			error += System.getProperty("line.separator") + jerarquia.getObjetoDiagramaDecorado().getNombre();
	
		return error;
	}
	
	private boolean estaEnElStack( Stack<JerarquiaGraficable> antecesores, JerarquiaGraficable jerarquiaABuscar ) 
	{
		for ( JerarquiaGraficable jerarquia: antecesores )
		{
			if ( jerarquia == jerarquiaABuscar )
				return true;
		}
		
		return false;
	}

	@Override
	public IMemento getMementoGenerico() {
		return this.getMementoEspecializado();
	}

	@Override
	public IMemento getMementoEspecializado() {
		return new MementoDiagramaGraficable(this);
	}

	@Override
	public void saveMemento(IMemento memento) {
		memento.cargarMemento();
	}
}
